package com.zt.teacher.filter;

import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.zt.common.common.ApiException;
import com.zt.common.pojo.common.JwtToken;
import com.zt.common.utils.ApplicationContextUtil;
import com.zt.common.utils.JwtUtil;
import com.zt.common.utils.RedisUtil;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.Objects;

/**
 * description:
 * author:zt
 * date:2021-10-23
 */

/**
 * 过滤器 在登陆完成后 拦截需要处理的接口
 * 执行顺序：preHandle-->isAccessAllowed-->isLoginTTempt-->executeLogin
 */
@Component
public class JwtFilter extends BasicHttpAuthenticationFilter {

    private static final Log log = LogFactory.get();

    /**
     * 解决跨域
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        // 跨域时会首先发送一个option请求，这里我们给option请求直接返回正常状态'
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return true;
        }
        return super.preHandle(request, response);
    }


    /**
     * 过滤器拦截请求的入口方法
     * 返回 true 则允许访问
     * 返回false 则禁止访问，会进入 onAccessDenied()
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {

        if (isLoginAttempt(request,response)){
            try {
                executeLogin(request,response);
                return true;
            }
            catch (Exception e){

                String msg = e.getMessage();
                Throwable cause = e.getCause();
                //当且仅当 JwtRealm校验 access token过期时需要刷新token
                if (cause instanceof TokenExpiredException){
                    String result = refreshToken(request, response);
                    if (result.equals("success")){
                        //刷新access token成功
                        log.info("刷新token成功");
                        return true;
                    }else if (result.equals("refresh token expire")){
                        //此处为在jwtRealm中 access token过期但refresh token未过期，进入jwtFilter中的refreshToken方法时 refresh token过期
                        log.info("refresh token过期");
                        responseTokenExpire(response);
                        return false;
                    }
                    log.error(result);
                    msg = result;
                    responseError(response,msg);
                    return false;

                } else if (cause instanceof ApiException){
                    //JwtRealm校验抛出ApiException 即为自定义状态 refresh token过期
                    responseTokenExpire(response);
                    return false;
                }
                else {
                    responseError(response,msg);
                    return false;
                }
            }
        } else{
            responseError(response,"未携带token");
            return false;
        }
    }

    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        HttpServletRequest req = (HttpServletRequest) request;
        String token = req.getHeader("Authorization");
        return token != null;
    }

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {

        HttpServletRequest req = (HttpServletRequest) request;
        String token = req.getHeader("Authorization");
        JwtToken jwtToken = new JwtToken(token);

        log.info("正在校验openId :" + jwtToken.getPrincipal());
        //此处调用jwtRealm进行token验证
        getSubject(request,response).login(jwtToken);
        return true;
    }

    //刷新token 只有access token过期且refresh token没过期 会执行
    private String refreshToken(ServletRequest request,ServletResponse response) {

        HttpServletRequest req= (HttpServletRequest) request;
        RedisUtil redisUtil = ApplicationContextUtil.get(RedisUtil.class);
        //获取传递过来的accessToken 即 jwt自带token
        String accessToken = req.getHeader("Authorization");
        //获取token里面的openId
        String teacherId = JwtUtil.decodedGetID(accessToken);

        //判断refreshToken是否过期了，过期了那么所含的openId的键不存在
        log.info("redisUtil.hasKey(openId)："+redisUtil.hasKey(teacherId));
        if (redisUtil.hasKey(teacherId)){
            //判断refresh的时间节点和传递过来的accessToken的时间节点是否一致，不一致校验失败
            long current = Long.parseLong(redisUtil.get(teacherId).toString());
            String expire = JwtUtil.getExpire(accessToken);
            if (current == Long.parseLong(expire)){
                //生成刷新后的token
                String token = JwtUtil.getToken(teacherId,String.valueOf(System.currentTimeMillis()), Integer.parseInt(Objects.requireNonNull(JwtUtil.getIdentity(accessToken))));
                //todo 需不需要 再次交给shiro进行认证
//                JwtToken jwtToken=new JwtToken(token);
                try {
//                    getSubject(request, response).login(jwtToken);
                    // 最后将刷新的AccessToken存放在Response的Header中的Authorization字段返回
                    HttpServletResponse httpServletResponse = (HttpServletResponse) response;
                    httpServletResponse.setHeader("authorization", token);
                    httpServletResponse.setHeader("access-Control-Expose-Headers", "Authorization");
                    return "success";
                }catch (Exception e){
                    return e.getMessage();
                }
            }else {
                //refresh token未过期，用户没有携带最新的access token
                return "didn't update access token";
            }
        }
        return "refresh token expire";
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        return false;
    }


    /**
     * 将非法请求跳转到 /unauthorized/**
     */
    private void responseError(ServletResponse response, String message) {
        log.error("responseError：" + message);

        try {
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            //设置编码，否则中文字符在重定向时会变为空字符串
            message = URLEncoder.encode(message, "UTF-8");
            httpServletResponse.sendRedirect("/exception/unauthorized/" + message);
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }

    /**
     * 将refresh token 过期异常单独处理
     */
    private void responseTokenExpire(ServletResponse response) {
        try {
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            httpServletResponse.sendRedirect("/exception/tokenExpire");
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }
}
